import { relationalStore } from '@kit.ArkData'
import { Check } from '../common/Check'
import { Internal } from '../common/Internal'
import { DatabaseSource, PickModelType, RdbPredicateScope } from '../common/Types'
import { ColumnType } from '../schema/Column'
import { Table } from '../schema/Table'
import { BaseDatabase } from '../common/BaseDatabase'
import { RdbPredicateWrapper } from '../common/RdbPredicateWrapper'
import { SQLiteDatabase } from './SQLiteDatabase'

export class DatabaseDao<M> extends BaseDatabase {
  private readonly db: SQLiteDatabase

  protected constructor(
    database: DatabaseSource,
    private readonly targetTable: Table<M>) {
    super(database)
    this.db = SQLiteDatabase.form(database)
  }

  /**
   * 将一行数据插入到目标表中
   *
   * @param value 要插入的数据
   * @param conflict 遇到冲突的解决模式
   * @returns 如果操作成功，返回行 ID，否则返回 -1
   */
  insert(
    value: M,
    conflict: relationalStore.ConflictResolution = relationalStore.ConflictResolution.ON_CONFLICT_NONE): Promise<number> {
    return this.db.insert(this.targetTable, value, conflict)
  }

  /**
   * 插入一组数据到目标表中
   *
   * @param value 要插入的数据
   * @returns 如果操作成功，返回插入的数据个数，否则返回 -1
   */
  batchInsert(value: M[]): Promise<number> {
    return this.db.batchInsert(this.targetTable, value)
  }

  /**
   * 根据实体中的主键来更新数据库中的数据
   *
   * @param value 要更新的实体
   * @returns 返回受影响的行数
   * @throws 不存在主键时将终止
   */
  update(
    value: M,
    conflict: relationalStore.ConflictResolution = relationalStore.ConflictResolution.ON_CONFLICT_NONE): Promise<number> {
    return this.db.update(this.targetTable, value, conflict)
  }

  /**
   *  根据条件更新数据库中的数据
   *
   * @param predicate 条件
   * @param value 要更新的数据
   * @param conflict 遇到冲突的解决模式
   * @returns 返回受影响的行数
   */
  updateIf(
    predicate: RdbPredicateScope<M>,
    value: Partial<M>,
    conflict: relationalStore.ConflictResolution = relationalStore.ConflictResolution.ON_CONFLICT_NONE): Promise<number> {
    return this.db.updateIf(this.targetTable, predicate(RdbPredicateWrapper.select(this.targetTable)), value, conflict)
  }

  /**
   * 根据实体中的主键来删除数据库中的数据
   *
   * @param value 要删除的实体
   * @returns 返回受影响的行数
   * @throws 不存在主键时将终止
   */
  delete(value: M): Promise<number> {
    return this.db.delete(this.targetTable, value)
  }

  /**
   * 根据条件删除数据库中的数据
   *
   * @param predicates 条件
   * @returns 返回受影响的行数
   */
  deleteIf(predicate: RdbPredicateScope<M>) {
    return this.db.deleteIf(this.targetTable, predicate)
  }

  /**
   * 根据条件查询数据库中的数据
   *
   * @param predicates 条件
   * @param columns 指定查询的列，为空则查询全部列
   * @returns 以实体数组的形式返回查询到的数据
   */
  query<T extends ColumnType[]>(
    predicates: RdbPredicateScope<M> = it => it,
    ...columns: T): Promise<PickModelType<M, T>[]> {
    return this.db.query(this.targetTable, predicates, ...columns)
  }

  /**
   * 根据条件查询数据库中的数据
   *
   * @param predicates 条件
   * @param columns 指定查询的列，为空则查询全部列
   * @returns 以单个实体的形式返回查询到的数据
   */
  queryOne<T extends ColumnType[]>(
    predicates: RdbPredicateScope<M> = it => it,
    ...columns: T): Promise<PickModelType<M, T>> {
    return this.db.queryOne(this.targetTable, predicates, ...columns)
  }

  /**
   * 根据条件查询数据库中的数据
   *
   * @param predicates 条件
   * @param columns 指定查询的列，为空则查询全部列
   * @returns 以 Model 包装的实体的数组形式返回查询到的数据
   */
  toList<T extends ColumnType[]>(
    predicates: RdbPredicateScope<M> = it => it,
    ...columns: T): Promise<Model<PickModelType<M, T>>[]> {
    return this.db.query(this.targetTable, predicates, ...columns)
      .then(it => it.map(item => Model.createModel(this, item)))
  }

  /**
   * 根据条件查询数据库中的数据
   *
   * @param predicates 条件
   * @param columns 指定查询的列，为空则查询全部列
   * @returns 以 Model 包装的实体的形式返回查询到的数据
   */
  first<T extends ColumnType[]>(
    predicates: RdbPredicateScope<M> = it => it,
    ...columns: T): Promise<Model<PickModelType<M, T>>> {
    return this.db.queryOne(this.targetTable, predicates, ...columns).then(result => Model.createModel(this, result))
  }

  /**
   * 执行事务操作，失败自动回退
   *
   * @param scope 事务处理函数，接收当前 DatabaseDao 实例作为参数
   * @returns Promise
   */
  async launchTransaction<T>(scope: (it: this) => T): Promise<T> {
    return this.db.launchTransaction(it => {
      return scope(this)
    })
  }

  /**
   * 用于创建 DatabaseDao 的实例
   *
   * @param database 数据源，支持 RdbStore、Database、Dao
   * @returns DatabaseDao 的建造者
   */
  static form(database: DatabaseSource) {
    return {
      select<T>(table: Table<T>) {
        return new DatabaseDao(database, table)
      }
    }
  }
}

namespace Model {
  export function createModel<M>(dao: DatabaseDao<M>, model: M) {
    const targetTable = dao['targetTable']
    const result: Model<M> = {
      set: async (key: string, value: any) => {
        Check.checkTableHasAtMostOneIdColumn(targetTable)
        const idColumn = targetTable[Internal.TableOptions].idColumns[0]
        if (idColumn.fieldName === key) {
          throw Error('It is not allowed to modify the value of the IdColumn.')
        }
        result[key] = value
        return dao.updateIf(it => it.equalTo(idColumn, model[idColumn.fieldName]), { [key]: value } as M)
      },
      flushChanges: async () => {
        return dao.update(result)
      },
      delete: async () => {
        return dao.delete(result)
      },
      ...model
    } as Model<M>
    return result
  }
}

/**
 * 结合实体属性和数据操作方法的复合类型
 */
export type Model<T> = {
  /**
   * 立即将模型的所有字段更新至数据库中对应的记录
   * 当变更的属性不明确时使用，如果变更的属性明确，请使用 set API
   *
   * @returns 返回受影响行数
   */
  flushChanges(): Promise<number>

  /**
   * 删除当前模型在数据库中对应的记录
   *
   * @returns 返回受影响行数
   */
  delete(): Promise<number>

  /**
   * 设置模型字段值并立即更新至数据库中对应的记录
   *
   * @param key 要设置的字段名
   * @param value 要设置的字段值
   * @returns 返回受影响行数
   */
  set<K extends keyof T>(key: K, value: T[K]): Promise<number>

} & T